package org.breathe.database;

import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.breathe.datastruct.RedisBytes;
import org.breathe.datastruct.RedisData;
import org.breathe.internal.Dict;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.*;

/**
 * Redis 数据库
 * @author: breathe
 * @createTime: 2025-05-12
 */
@Getter
@Setter
@Slf4j
public class RedisDB {
    private final Dict<RedisBytes, RedisData> data;
    private final Dict<RedisBytes, RedisData> store;
    private final int id;
    private final ScheduledExecutorService cleaner = Executors.newSingleThreadScheduledExecutor();
    private static final int CLEANUP_SAMPLE_SIZE = 20;
    public RedisDB(int id) {
        this.id = id;
        this.data = new Dict<>();
        this.store = new Dict<>();
        cleaner.scheduleAtFixedRate(this::cleanUpExpiredKeys, 1, 5, TimeUnit.SECONDS);
    }
    public Set<RedisBytes> keys() {
        return data.keySet();
    }
    public boolean exist(RedisBytes key) {
        return data.containsKey(key);
    }
    public synchronized void put(RedisBytes key, RedisData value) {
        data.put(key, value);
        if (value.timeout() != -1) {
            store.put(key, value);
            log.info("key be added: {}", key);
        }
    }
    public synchronized RedisData get(RedisBytes key) {
        RedisData redisData = data.get(key);
        if (redisData != null && redisData.timeout() != -1 && redisData.timeout() < System.currentTimeMillis()) {
            data.remove(key);
            store.remove(key);
            log.info("key be removed: {}", key);
            return null;
        }
        return redisData;
    }
    public synchronized void remove(RedisBytes key) {
        data.remove(key);
        store.remove(key);
        log.info("key be removed: {}", key);
    }
    public int size() {
        return data.size();
    }
    private synchronized void cleanUpExpiredKeys() {
        List<RedisBytes> keys = new ArrayList<>(store.keySet());
        int size = keys.size();
        if (size == 0) {
            return ;
        }
        Random random = ThreadLocalRandom.current();
        int sampleSize = Math.min(CLEANUP_SAMPLE_SIZE, size);
        for (int i = 0; i < sampleSize; i++) {
            RedisBytes key = keys.get(random.nextInt(size));
            RedisData redisData = store.get(key);
            if (redisData != null && redisData.timeout() < System.currentTimeMillis()) {
                data.remove(key);
                store.remove(key);
                log.info("key be removed: {}", key);
            }
        }
    }
}
